##############################################################################
##
##     Copyright University of Winnipeg, 2008
##     Author: Sydney Weidman s.weidman@uwinnipeg.ca
##
##     This program is free software: you can redistribute it and/or modify
##     it under the terms of the GNU General Public License as published by
##     the Free Software Foundation, either version 3 of the License, or
##     (at your option) any later version.
##
##     This program is distributed in the hope that it will be useful,
##     but WITHOUT ANY WARRANTY; without even the implied warranty of
##     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##     GNU General Public License for more details.
##
##     You should have received a copy of the GNU General Public License
##     along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##############################################################################

from django.db import models
from django.db.models import CharField, FileField, TextField, ImageField
from django.db.models import ManyToManyField, Q
from django.db.models import DateField, BooleanField, ForeignKey, EmailField
from xml.dom.minidom import getDOMImplementation
import base64
import os
from cStringIO import StringIO

JOURNAL_EMAIL = 's.weidman@uwinnipeg.ca'

# Create your models here.

ISSUE_ENTITIES = ('title','description','volume','number','year',
                      'date_published', 'open_access', 'access_date')

ISSUE_ATTRIBUTES = ('identification', 'published', 'current')

SECTION_ENTITIES = ('title','abbrev')

ARTICLE_ENTITIES = ('title', 'abstract', 'date_published','open_access',
                    'author', 'indexing','pages','galley')
INDEXING_ENTITIES = ('discipline','type','subject','subject_class')

AUTHOR_ENTITIES = ('firstname','middlename','lastname','affiliation',
                   'email','biography')

VALID_MIME_TYPES = (
    ('jpg','application/jpg'),
    ('pdf','application/pdf'),
    ('png','application/png'),
    ('txt','text/plain'),
    ('htm','text/html'),)

START_YEAR = 1960
END_YEAR = 2020

VALID_YEARS = range(START_YEAR, END_YEAR + 1)
VALID_YEARS = tuple(zip(VALID_YEARS,VALID_YEARS))

DISCIPLINE_LIST = ['chemistry',
                   'physics',
                   'biology',
                   'mathematics',
                   'english',
                   'geography']

DISCIPLINES = tuple(enumerate(DISCIPLINE_LIST))

LANG_LIST = ['en','fr']

IDENTIFIERS = (('title','Title'),
               ('num_vol_year', 'Number, Volume, Year'),
               ('vol_year','Volume, Year'),
               ('year', 'Year'))

LANGS = tuple(zip(LANG_LIST,LANG_LIST))


class Author(models.Model):

    firstname = CharField(max_length=50)
    middlename = CharField(max_length=50, blank=True)
    lastname = CharField(max_length=50)
    affiliation = CharField(max_length=200, blank=True)
    email = EmailField(blank=True, default=JOURNAL_EMAIL)
    biography = TextField(blank=True)
    primary_contact = BooleanField(default=False)

    def __str__(self):
        return "%s, %s" % (self.lastname, self.firstname)

    def asXML(self, node=None):
        """Return this instance as an XML snippet based on native.dtd
        node is a DOM document element that provides a factory interface
        """
        author_element = node.createElement('author')
        author_element.setAttribute('primary_contact',
                                    str(self.primary_contact).lower())
        for ent in AUTHOR_ENTITIES:
            try:
                val = str(getattr(self, ent)).strip()
            except AttributeError:
                continue
            if val:
                child = node.createElement(ent)
                cdata = node.createTextNode(val)
                child.appendChild(cdata)
                author_element.appendChild(child)
        return author_element
    
class Article(models.Model):

    title = CharField(max_length=255)
    abstract = TextField(blank=True)
    date_published = DateField(blank=True)
    language = CharField(max_length=2, choices=LANGS, default='en')
    open_access = BooleanField(default=False, blank=True)
    discipline = CharField(max_length=50, blank=True)
    work_type = CharField(max_length=100, blank=True)
    subject = CharField(max_length=100, blank=True)
    subject_class = CharField(max_length=100, blank=True)
    pages = CharField(max_length=12, blank=True)
    pdf_file = FileField(upload_to='articles/%Y/%m/%d', blank=True)
    authors = ManyToManyField('Author')
    issue = ForeignKey('Issue')
    section = ForeignKey('Section')
    
    def __str__(self):
        return self.title

    def _getTextNode(self, fieldname, document):
        """Create a DOM element named fieldname
        and populate it with data from the field. document is the
        DOM document element to be used for element factory
        return a node object
        """
        try:
            val = str(getattr(self, fieldname)).strip()
        except AttributeError:
            return None
        if val == '':
            return None
        child = document.createElement(fieldname)
        cdata = document.createTextNode(val)
        child.appendChild(cdata)
        return child

    def _getBooleanNode(self, fieldname, document):
        """Create a boolean element. Return None if self.fieldname is false"""
        if getattr(self, fieldname):
            child = document.createElement(fieldname)
            return child
        else:
            return None

    def _getEncodedFile(self):
        """Base 64 encode a file and return the entire string
        if self.pdf_file is not set, return None"""
        if not self.has_file():
            return None
        f = open(self.get_pdf_file_filename())
        o = StringIO()
        base64.encode(f,o)
        f.close()
        return o.getvalue()
    
    def _getGalleyElement(self, node):
        """Create a galley element, populate it, and return it.
        node is a DOM documentElement used as a node factory.
        If self.pdf_file is not set, return None
        """
        if not self.pdf_file:
            return None
        galley_element = node.createElement('galley')
        label = node.createElement('label')
        label.appendChild(node.createTextNode('PDF'))
        galley_element.appendChild(label)
        file_element = node.createElement('file')
        embed_element = node.createElement('embed')
        # get the base filename
        fname = self.pdf_file.split('/')[-1]
        embed_element.setAttribute('filename',fname)
        embed_element.setAttribute('mime_type','application/x-pdf')
        embed_element.setAttribute('encoding', 'base64')
        thefile = self._getEncodedFile()
        encoded_data = node.createTextNode(thefile)
        embed_element.appendChild(encoded_data)
        file_element.appendChild(embed_element)
        galley_element.appendChild(file_element)
        return galley_element
        
    def has_file(self):
        """If the pdf_file field is set, return True
        otherwise, return False
        """
        if len(self.pdf_file) > 0 and not self.pdf_file.isspace():
            return True
        else:
            return False
        
    def asXML(self, node=None):
        """Return this instance as an XML snippet based on native.dtd
        node is an instance of a DOM document
        """
        if node is not None:
            article_element = node.createElement('article')
            article_element.setAttribute('language',
                                         getattr(self,'language').lower())
            for ent in ARTICLE_ENTITIES:
                try:
                    val = getattr(self,ent)
                except AttributeError:
                    pass
                if ent == 'open_access':
                    oa_node = self._getBooleanNode(ent, node)
                    if oa_node:
                        article_element.appendChild(oa_node)
                    continue
                if ent == 'indexing':
                    # this node has sub-elements
                    has_indexing = False
                    child = node.createElement(ent)
                    for i in INDEXING_ENTITIES:
                        sub_elem = self._getTextNode(i, node)
                        if sub_elem:
                            has_indexing = True
                            child.appendChild(sub_elem)
                    if has_indexing:
                        article_element.appendChild(child)
                    continue
                if ent == 'galley':
                    # add the file inside a galley element
                    # the galley xml bit is constructed of an embed
                    galley_element = self._getGalleyElement(node)
                    if galley_element:
                        article_element.appendChild(galley_element)
                    continue
                if ent == 'author':
                    authors = self.authors.all()
                    for author in authors:
                        article_element.appendChild(author.asXML(node))
                    continue
                # get the rest of the data into text nodes
                # and add it to the element
                child = node.createElement(ent)
                cdata = node.createTextNode(str(val))
                child.appendChild(cdata)
                article_element.appendChild(child)
            return article_element
    
class Section(models.Model):

    title = CharField(max_length=100)
    abbrev = CharField(max_length=20)

    def __str__(self):
        return self.title
    
    def asXML(self, articles, node=None):
        """Return all articles in a section as
        an XML snippet based on native.dtd
        node is a DOM document object"""
        if node is None:
            return "<section>%s</section>" % (self.title,)
        else:
            # get all articles in the section
            sec = node.createElement('section')
            for ent in SECTION_ENTITIES:
                data = getattr(self,ent)
                if data is not None:
                    child = node.createElement(ent)
                    cdata = node.createTextNode(str(data))
                    child.appendChild(cdata)
                sec.appendChild(child)
            for article in articles:
                sec.appendChild(article.asXML(node))            
            return sec

class Issue(models.Model):

    title = CharField(max_length=255)
    description = TextField(blank=True)
    volume = CharField(max_length=10)
    identification = CharField(max_length=12, choices=IDENTIFIERS,
                               default='num_vol_year', blank=True)
    current = BooleanField(default=False, blank=True)
    published = BooleanField(default=False, blank=True)
    number = CharField(max_length=10)
    year = CharField(max_length=4, choices=VALID_YEARS)
    date_published = DateField(blank=True)
    access_date = DateField(blank=True, null=True)
    open_access = BooleanField()

    def files_missing(self):
        """Returns true if any articles in the article set are
        missing pdf_file."""
        articles = self.article_set.all()
        for article in articles:
            if not article.has_file():
                return True
            
    def asXML(self, node=None):
        """Render this entire Issue as an XML file"""
        # first sections, then articles
        impl = getDOMImplementation()
        doctype = impl.createDocumentType('issue','','native.dtd')
        newdoc = impl.createDocument('issue','issue',doctype)
        root = newdoc.documentElement
        for att in ISSUE_ATTRIBUTES:
            root.setAttribute(att, str(getattr(self, att)).lower())
        for ent in ISSUE_ENTITIES:
            data = getattr(self,ent)
            if data is not None:
                # handle open_access - no cdata needed
                if ent == 'open_access':
                    node = newdoc.createElement(ent)
                    root.appendChild(node)
                    continue
                node = newdoc.createElement(ent)
                cdata = newdoc.createTextNode(str(data))
                node.appendChild(cdata)
                root.appendChild(node)
        sections = Section.objects.all()
        for sect in sections:
            articles = self.article_set.filter(section__exact=sect.id).order_by('id')
            if articles.count() > 0:
                root.appendChild(sect.asXML(articles, newdoc))
        return newdoc.toxml()
    
    def __str__(self):
        return self.title
